#include "DiffHLEngine.h"
#include "Utils.h"
#include "LogManager.h"
#include "ConfigValues.h"
#include "Defines.h"
#include <sstream>
#include <pcre/pcre.h>
#include <libxslt/xsltInternals.h>
#include <libxslt/xsltutils.h>
#include <libxslt/transform.h>
#include <libxml/uri.h>

#define MAX_FEED_SIZE 10

#define BREAK_TAGS_COUNT 13

static const char* break_tags[BREAK_TAGS_COUNT] = { "br", "p", "table", "tr", "div", "hr", "pre",
                                                    "dl", "dt", "dd", "ul", "ol", "li"} ;

DiffHLEngine::DiffHLEngine(void)
{
    prevHtmlDoc = NULL;
    curHtmlDoc = NULL;
    
    changesSummary = htmlNewDoc(NULL, NULL);
    xmlDocSetRootElement(changesSummary, xmlNewNode(NULL, (const xmlChar*)"span"));
    htmlSetMetaEncoding(changesSummary, (const xmlChar*)W3M_ENCODING);
}

DiffHLEngine::~DiffHLEngine(void)
{
    if (prevHtmlDoc)
        xmlFreeDoc(prevHtmlDoc);
    if (curHtmlDoc)
        xmlFreeDoc(curHtmlDoc);
    if (changesSummary)
        xmlFreeDoc(changesSummary);
    
    xmlCleanupCharEncodingHandlers();
    xmlResetLastError();
}

bool DiffHLEngine::SetCurrentResource(  const string& szResource,
                                        const string& szUserCharset,
                                        const string& szServerCharset,
                                        const string& szEffectiveUrl,
                                        unsigned int& uiContentType)
{
    uiCurContentType = uiContentType;
    szCurEffectiveUrl = szEffectiveUrl;
    switch (uiContentType)
    {
    case CONTENT_TYPE_XML:    
    case CONTENT_TYPE_RSS:
    case CONTENT_TYPE_ATOM:
    case CONTENT_TYPE_XML | CONTENT_TYPE_RSS:
    case CONTENT_TYPE_XML | CONTENT_TYPE_ATOM:
        {
            string szHtmlResource;
            ProcessXSLT(szResource, szHtmlResource, uiContentType);
            curHtmlDoc = htmlReadMemory(    szHtmlResource.c_str(),
                                            szHtmlResource.size(),
                                            szEffectiveUrl.c_str(),
                                            NULL, 
                                            HTML_PARSE_RECOVER | HTML_PARSE_NOBLANKS | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING);
            break;
        }
    case CONTENT_TYPE_HTML:
        {
            if (szUserCharset.size())
            {
                curHtmlDoc = htmlReadMemory(   szResource.c_str(),
                                               szResource.size(),
                                               szEffectiveUrl.c_str(),
                                               szUserCharset.c_str(),
                                               HTML_PARSE_RECOVER | HTML_PARSE_NOBLANKS | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING);
                if (curHtmlDoc)
                    htmlSetMetaEncoding(curHtmlDoc, (const xmlChar*)szUserCharset.c_str());
            }
            else
            if (szServerCharset.size())
            {
                curHtmlDoc = htmlReadMemory(    szResource.c_str(),
                                                szResource.size(),
                                                szEffectiveUrl.c_str(),
                                                szServerCharset.c_str(),
                                                HTML_PARSE_RECOVER | HTML_PARSE_NOBLANKS | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING);
                if (curHtmlDoc)
                  htmlSetMetaEncoding(curHtmlDoc, (const xmlChar*)szServerCharset.c_str());
            }           
            else
            {
                string szResourceCharset;
                GetContentCharsetFromResource(szResource, szResourceCharset);
                if (szResourceCharset.size())
                {
                    curHtmlDoc = htmlReadMemory(    szResource.c_str(),
                                                    szResource.size(),
                                                    szEffectiveUrl.c_str(),
                                                    szResourceCharset.c_str(),
                                                    HTML_PARSE_RECOVER | HTML_PARSE_NOBLANKS | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING);
                    if (curHtmlDoc)
                        htmlSetMetaEncoding(curHtmlDoc, (const xmlChar*)szResourceCharset.c_str());
                }
                else
                {
                    curHtmlDoc = htmlReadMemory(    szResource.c_str(),
                                                    szResource.size(),
                                                    szEffectiveUrl.c_str(),
                                                    "//IGNORE",
                                                    HTML_PARSE_RECOVER | HTML_PARSE_NOBLANKS | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING);
                }
            }
        }
    }
    if (curHtmlDoc == NULL && szResource.size() != 0)
    {
        return false;
    }
    else
    {
        return true;
    }
}

bool DiffHLEngine::SetPreviousResource( const string& szResource,
                                        const string& szUserCharset,
                                        const string& szServerCharset,
                                        const string& szEffectiveUrl,
                                        unsigned int& uiContentType)
{
    uiPrevContentType = uiContentType;
    szPrevEffectiveUrl = szEffectiveUrl;
    switch (uiContentType)
    {
    case CONTENT_TYPE_XML:
    case CONTENT_TYPE_RSS:
    case CONTENT_TYPE_ATOM:
    case CONTENT_TYPE_XML | CONTENT_TYPE_RSS:
    case CONTENT_TYPE_XML | CONTENT_TYPE_ATOM:
        {
            string szHtmlResource;
            ProcessXSLT(szResource, szHtmlResource, uiContentType);
            prevHtmlDoc = htmlReadMemory(   szHtmlResource.c_str(),
                                            szHtmlResource.size(),
                                            szEffectiveUrl.c_str(),
                                            NULL,
                                            HTML_PARSE_RECOVER | HTML_PARSE_NOBLANKS | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING);
            break;
        }
    case CONTENT_TYPE_HTML:
        {
            if (szUserCharset.size())
            {
                prevHtmlDoc = htmlReadMemory(   szResource.c_str(),
                                                szResource.size(),
                                                szEffectiveUrl.c_str(),
                                                szUserCharset.c_str(),
                                                HTML_PARSE_RECOVER | HTML_PARSE_NOBLANKS | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING);
                if (prevHtmlDoc)
                    htmlSetMetaEncoding(prevHtmlDoc, (const xmlChar*)szUserCharset.c_str());
            }
            else
            if (szServerCharset.size())
            {
                prevHtmlDoc = htmlReadMemory(   szResource.c_str(),
                                                szResource.size(),
                                                szEffectiveUrl.c_str(),
                                                szServerCharset.c_str(),
                                                HTML_PARSE_RECOVER | HTML_PARSE_NOBLANKS | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING);
                if (prevHtmlDoc)
                    htmlSetMetaEncoding(prevHtmlDoc, (const xmlChar*)szServerCharset.c_str());
            }
            else
            {
                string szResourceCharset;
                GetContentCharsetFromResource(szResource, szResourceCharset);
                if (szResourceCharset.size())
                {
                    prevHtmlDoc = htmlReadMemory(   szResource.c_str(),
                                                    szResource.size(),
                                                    szEffectiveUrl.c_str(),
                                                    szResourceCharset.c_str(),
                                                    HTML_PARSE_RECOVER | HTML_PARSE_NOBLANKS | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING);
                    if (prevHtmlDoc)
                        htmlSetMetaEncoding(prevHtmlDoc, (const xmlChar*)szResourceCharset.c_str());
                }
                else
                {
                    prevHtmlDoc = htmlReadMemory(   szResource.c_str(),
                                                    szResource.size(),
                                                    szEffectiveUrl.c_str(),
                                                    "//IGNORE",
                                                    HTML_PARSE_RECOVER | HTML_PARSE_NOBLANKS | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING);
                }
            }
        }
    }
    if (prevHtmlDoc == NULL && szResource.size() != 0)
    {
        return false;
    }
    else
    {
        return true;
    }
}

void DiffHLEngine::Process(     const int& iHLFontColor, const int& iHLBkColor, const unsigned int& uiHLFontStyle,
                                const string& szXPath, const unsigned int& uiTriggerType, const string& szTriggerParams,
                                bool& bDiffCacheInput, bool& bDiffCacheOutput, string& szDiffCache, double& fDiffTotalTime,
                                xmlDocPtr& pDiffHLResult, xmlDocPtr& pChangesSummary,                                
                                unsigned int& uiBlocksAdded, unsigned int& uiBlocksDeleted,
                                unsigned int& uiQtyTriggers, unsigned int& uiQtyAnchors)
{
    bool bIsFirstDHL = true;
    vector<XmlNodeHolder> prevNodes;
    vector<XmlNodeHolder> curNodes;
    vector<XmlNodeHolder> diffNodes;

    pDiffHLResult = NULL;
    pChangesSummary = changesSummary;
    uiQtyTriggers = 0;
    uiQtyAnchors = 0;
    uiBlocksAdded = 0;
    uiBlocksDeleted = 0;
    
    if (prevHtmlDoc)
    {
        bIsFirstDHL = false;
        vector<xmlNodePtr> xPathNodes;

        if (szXPath.size() != 0)
        {
            xmlXPathContextPtr xPathCtx = NULL; 
            xmlXPathObjectPtr xPathObj = NULL;

            xPathCtx = xmlXPathNewContext(prevHtmlDoc);
            if(xPathCtx != NULL)
            {
                xPathObj = xmlXPathEvalExpression((const xmlChar*)szXPath.c_str(), xPathCtx);
                if (xPathObj != NULL)
                {
                    int size = (xPathObj->nodesetval) ? xPathObj->nodesetval->nodeNr : 0;
                    
                    for(int i = size - 1; i >= 0; i--)
                    {
                        if (xPathObj->nodesetval->nodeTab[i]->type != XML_NAMESPACE_DECL)
                        {
                            xmlNodePtr rootNode = xPathObj->nodesetval->nodeTab[i];
                            if (rootNode != NULL)
                            {
                                xPathNodes.push_back(rootNode);
                                if (iHLBkColor != -1)
                                    InsertXPathRegionTag(rootNode, iHLBkColor ^ 0x00FFFFFF);
                                else
                                    InsertXPathRegionTag(rootNode, 0x00FFFF00);
                            }
                            xPathObj->nodesetval->nodeTab[i] = NULL;
                        }
                    }
                }
            }

            if (xPathObj != NULL)
                xmlXPathFreeObject(xPathObj);
            if (xPathCtx != NULL)
                xmlXPathFreeContext(xPathCtx); 
        }

        if (xPathNodes.size() > 0)
        {
            for (vector<xmlNodePtr>::iterator iter = xPathNodes.begin(); iter != xPathNodes.end(); iter++)
            {
                GetAllTextNodes((*iter)->children, prevNodes);
            }
        }
        else
        {
            xmlNodePtr rootNode = NULL;
            GetFirstNodeByName(xmlDocGetRootElement(prevHtmlDoc), "body", rootNode);
            if (rootNode)
                GetAllTextNodes(rootNode, prevNodes);
            else
                GetAllTextNodes(xmlDocGetRootElement(prevHtmlDoc), prevNodes);
       }
    }
    
    if (curHtmlDoc)
    {
        vector<xmlNodePtr> xPathNodes;

        if (szXPath.size() != 0)
        {
            xmlXPathContextPtr xPathCtx = NULL; 
            xmlXPathObjectPtr xPathObj = NULL;

            xPathCtx = xmlXPathNewContext(curHtmlDoc);
            if(xPathCtx != NULL)
            {
                xPathObj = xmlXPathEvalExpression((const xmlChar*)szXPath.c_str(), xPathCtx);
                if (xPathObj != NULL)
                {
                    int size = (xPathObj->nodesetval) ? xPathObj->nodesetval->nodeNr : 0;
                    
                    for(int i = size - 1; i >= 0; i--)
                    {
                        xmlNodePtr rootNode = xPathObj->nodesetval->nodeTab[i];
                        if (rootNode != NULL)
                        {
                            xPathNodes.push_back(rootNode);
                            if (iHLBkColor != -1)
                                InsertXPathRegionTag(rootNode, iHLBkColor ^ 0x00FFFFFF);
                            else
                                InsertXPathRegionTag(rootNode, 0x00FFFF00);
                        }
                        xPathObj->nodesetval->nodeTab[i] = NULL;
                    }
                }
            }

            if (xPathObj != NULL)
                xmlXPathFreeObject(xPathObj);
            if (xPathCtx != NULL)
                xmlXPathFreeContext(xPathCtx); 
        }

        if (xPathNodes.size() > 0)
        {
            for (vector<xmlNodePtr>::iterator iter = xPathNodes.begin(); iter != xPathNodes.end(); iter++)
            {
                GetAllTextNodes((*iter)->children, curNodes);
            }
        }
        else
        {
            xmlNodePtr rootNode = NULL;
            GetFirstNodeByName(xmlDocGetRootElement(curHtmlDoc), "body", rootNode);
            if (rootNode)
                GetAllTextNodes(rootNode, curNodes);
            else
                GetAllTextNodes(xmlDocGetRootElement(curHtmlDoc), curNodes);
        }
    }
    
    MergerResultVector mergeResult;
    if (bDiffCacheInput)
    {
        StringToMergeResult(szDiffCache, mergeResult);
    }
    else
    {
        double fDiffStartTime = (double)GetTickCount();
        getMergeResult<XmlNodeHolder>(  prevNodes, prevNodes.begin(), prevNodes.end(),
                                        curNodes, curNodes.begin(), curNodes.end(),
                                        mergeResult, MRF_NEW | MRF_DELETED);        
        double fDiffEndTime = (double)GetTickCount();
        fDiffTotalTime = (fDiffEndTime - fDiffStartTime) / 1000;
        MergeResultToString(mergeResult, szDiffCache);
        bDiffCacheOutput = true;
    }
    MergerResultVector::iterator iter;
    for (iter = mergeResult.begin(); iter != mergeResult.end(); iter++)
    {
        for (int i = 0; i < iter->len; i++)
        {
            if (iter->status == MIS_DELETED)
            {
                uiBlocksDeleted++;
                if (uiTriggerType & TRIGGER_FLAG_IN_DEL_TEXT) 
                    diffNodes.push_back(*(prevNodes.begin() + (iter->offs + i)));                
            }
            if (iter->status == MIS_NEW)
            {
                uiBlocksAdded++;
                if (!(uiTriggerType & TRIGGER_FLAG_IN_DEL_TEXT)) 
                    diffNodes.push_back(*(curNodes.begin() + (iter->offs + i)));
            }
        }
    }

    if (uiCurContentType == CONTENT_TYPE_HTML)
    {
        if (uiTriggerType & TRIGGER_FLAG_IN_DEL_TEXT)
            InsertBaseTag(xmlDocGetRootElement(prevHtmlDoc), szPrevEffectiveUrl);
        else
            InsertBaseTag(xmlDocGetRootElement(curHtmlDoc), szCurEffectiveUrl);
    }
    
    switch (uiTriggerType & 0x0001FFFF)
    {
    case TRIGGER_ANY_CHANGES | TRIGGER_FLAG_IN_ADD_TEXT:
        {
            if (prevHtmlDoc)
            {
                for (unsigned long i = 0; i < diffNodes.size(); i++)
                {
                    xmlNodePtr hlRootNode = NULL;
                    xmlNodePtr hlLastNode = NULL;
                    if (!((uiTriggerType & TRIGGER_FLAG_SKIP_DIGITS) && IsStringDigits(diffNodes[i].node->content)) &&
                        !((uiTriggerType & TRIGGER_FLAG_SKIP_SMALL_CHANGES) && xmlStrlen(diffNodes[i].node->content) < SMALL_CHANGES_SIZE))
                    {
                        AddToChangesSummary(diffNodes[i].node, pChangesSummary);
                        InsertW3MLinkNode(diffNodes[i].node, uiQtyAnchors);
                        CreateHighLightNodes(iHLFontColor, iHLBkColor, uiHLFontStyle, hlRootNode, hlLastNode);
                        if (hlRootNode && hlLastNode)
                        {
                            xmlReplaceNode(diffNodes[i].node, hlRootNode);
                            xmlAddChild(hlLastNode, diffNodes[i].node);
                        }
                        uiQtyTriggers++;
                    }
                }
            }
            else
            {
                if (!bDiffCacheInput)
                    szDiffCache.clear();
            }
            pDiffHLResult = curHtmlDoc;
            break;
        }
    case TRIGGER_ANY_CHANGES | TRIGGER_FLAG_IN_DEL_TEXT:
        {
            if (prevHtmlDoc)
            {
                for (unsigned long i = 0; i < diffNodes.size(); i++)
                {
                    xmlNodePtr hlRootNode = NULL;
                    xmlNodePtr hlLastNode = NULL;
                    if (!((uiTriggerType & TRIGGER_FLAG_SKIP_DIGITS) && IsStringDigits(diffNodes[i].node->content)) &&
                        !((uiTriggerType & TRIGGER_FLAG_SKIP_SMALL_CHANGES) && xmlStrlen(diffNodes[i].node->content) < SMALL_CHANGES_SIZE))
                    {
                        AddToChangesSummary(diffNodes[i].node, pChangesSummary);
                        InsertW3MLinkNode(diffNodes[i].node, uiQtyAnchors);
                        CreateHighLightNodes(iHLFontColor, iHLBkColor, uiHLFontStyle, hlRootNode, hlLastNode);
                        if (hlRootNode && hlLastNode)
                        {
                            xmlReplaceNode(diffNodes[i].node, hlRootNode);
                            xmlAddChild(hlLastNode, diffNodes[i].node);
                        }
                        uiQtyTriggers++;
                    }
                }
                pDiffHLResult = prevHtmlDoc;
            }
            else
            {
                if (!bDiffCacheInput)
                    szDiffCache.clear();
                pDiffHLResult = curHtmlDoc;
            }
            break;
        }
    case TRIGGER_ANY_WORD_FROM_LIST | TRIGGER_FLAG_IN_ADD_TEXT:
        {
            if (diffNodes.size())
                HighLightAnyWordFromList(diffNodes, bIsFirstDHL, szTriggerParams, iHLFontColor, iHLBkColor, uiHLFontStyle, pChangesSummary, uiQtyTriggers, uiQtyAnchors);
            pDiffHLResult = curHtmlDoc;
            break;
        }
    case TRIGGER_ANY_WORD_FROM_LIST | TRIGGER_FLAG_IN_DEL_TEXT:
        {
            if (diffNodes.size())
                HighLightAnyWordFromList(diffNodes, bIsFirstDHL, szTriggerParams, iHLFontColor, iHLBkColor, uiHLFontStyle, pChangesSummary, uiQtyTriggers, uiQtyAnchors);
            pDiffHLResult = prevHtmlDoc;
            break;
        }
    case TRIGGER_ALL_WORDS_FROM_LIST | TRIGGER_FLAG_IN_ADD_TEXT:
        {
            if (diffNodes.size())
                HighLightAllWordsFromList(diffNodes, bIsFirstDHL, szTriggerParams, iHLFontColor, iHLBkColor, uiHLFontStyle, pChangesSummary, uiQtyTriggers, uiQtyAnchors);
            pDiffHLResult = curHtmlDoc;
            break;
        }
    case TRIGGER_ALL_WORDS_FROM_LIST | TRIGGER_FLAG_IN_DEL_TEXT:
        {
            if (diffNodes.size())
                HighLightAllWordsFromList(diffNodes, bIsFirstDHL, szTriggerParams, iHLFontColor, iHLBkColor, uiHLFontStyle, pChangesSummary, uiQtyTriggers, uiQtyAnchors);
            pDiffHLResult = prevHtmlDoc;
            break;
        }
    case TRIGGER_PHRASE | TRIGGER_FLAG_IN_ADD_TEXT:
        {
            if (diffNodes.size())
                HighLightPhrase(diffNodes, bIsFirstDHL, szTriggerParams, iHLFontColor, iHLBkColor, uiHLFontStyle, pChangesSummary, uiQtyTriggers, uiQtyAnchors);
            pDiffHLResult = curHtmlDoc;
            break;
        }
    case TRIGGER_PHRASE | TRIGGER_FLAG_IN_DEL_TEXT:
        {
            if (diffNodes.size())
                HighLightPhrase(diffNodes, bIsFirstDHL, szTriggerParams, iHLFontColor, iHLBkColor, uiHLFontStyle, pChangesSummary, uiQtyTriggers, uiQtyAnchors);
            pDiffHLResult = prevHtmlDoc;
            break;
        }
    case TRIGGER_REGEX | TRIGGER_FLAG_IN_ADD_TEXT:
        {
            if (diffNodes.size())
                HighLightRegex(diffNodes, bIsFirstDHL, szTriggerParams, iHLFontColor, iHLBkColor, uiHLFontStyle, pChangesSummary, uiQtyTriggers, uiQtyAnchors);
            pDiffHLResult = curHtmlDoc;
            break;
        }
    case TRIGGER_REGEX | TRIGGER_FLAG_IN_DEL_TEXT:
        {
            if (diffNodes.size())
                HighLightRegex(diffNodes, bIsFirstDHL, szTriggerParams, iHLFontColor, iHLBkColor, uiHLFontStyle, pChangesSummary, uiQtyTriggers, uiQtyAnchors);
            pDiffHLResult = prevHtmlDoc;
            break;
        }
    }
}

void DiffHLEngine::GetFaviconURLFromResource(string& szFaviconUrl)
{
    if (curHtmlDoc)
    {
        vector<xmlNodePtr> nodes;
        xmlNodePtr headNode = NULL;
        GetFirstNodeByName(xmlDocGetRootElement(curHtmlDoc), "head", headNode);
        if (headNode)
            GetAllNodesByName(headNode, "link", nodes);
        else
            GetAllNodesByName(xmlDocGetRootElement(curHtmlDoc), "link", nodes);
        for (vector<xmlNodePtr>::iterator iter = nodes.begin(); iter != nodes.end() && !szFaviconUrl.size(); iter++)
        {
            xmlChar* rel = xmlGetProp(*iter, (const xmlChar*)"rel");
            if (rel)
            {
                if (xmlStrcasecmp(rel, (const xmlChar*)"shortcut icon") == 0)
                {
                    xmlChar* href = xmlGetProp(*iter, (const xmlChar*)"href");
                    if (href)
                    {
                        xmlChar* uri = NULL;
                        uri = xmlBuildURI(href, (const xmlChar*)szCurEffectiveUrl.c_str());
                        if (uri)
                        {
                            szFaviconUrl = (const char*)uri;
                            xmlFree(uri);
                        }
                        xmlFree(href);
                    }
                }
                xmlFree(rel);
            }
        }
    }
    if (!szFaviconUrl.size())
    {
        xmlChar* uri = NULL;
        uri = xmlBuildURI((const xmlChar*)"/favicon.ico", (const xmlChar*)szCurEffectiveUrl.c_str());
        if (uri)
        {
            szFaviconUrl = (const char*)uri;
            xmlFree(uri);
        }        
    }
}

void DiffHLEngine::GetTitleFromResource(string& szTitle)
{
    if (curHtmlDoc)
    {
        xmlNodePtr titleNode = NULL;
        GetFirstNodeByName(xmlDocGetRootElement(curHtmlDoc), "title", titleNode);
        if (titleNode)
        {
            xmlNodePtr titleNodeText = titleNode->children;
            if (xmlNodeIsText(titleNodeText) && !xmlIsBlankNode(titleNodeText) && titleNodeText->content && !IsStringEmpty(titleNodeText->content))
            {
                szTitle = (const char*)titleNodeText->content;
            }
        }
    }
}

bool DiffHLEngine::DumpDiffHLResult(xmlDocPtr pDiffHLResult, string& szDiffHLResult)
{
    DumpHtmlDocToString(pDiffHLResult, szDiffHLResult);
    if (pDiffHLResult && szDiffHLResult.size() == 0)
    {
        return false;
    }
    else
    {
        return true;
    }
}

bool DiffHLEngine::DumpChangesSummary(xmlDocPtr pChangesSummary,
                                      const int& iHLFontColor, const int& iHLBkColor, const unsigned int& uiHLFontStyle,
                                      const unsigned int& uiTriggerType, const string& szTriggerParams,
                                      string& szChangesSummary)
{
    if (pChangesSummary)
    {
        if ((uiTriggerType & 0x0000FFFF) != TRIGGER_ANY_CHANGES)
        {
            vector<XmlNodeHolder> nodes;
            GetAllTextNodes(xmlDocGetRootElement(pChangesSummary), nodes);
            HighLightChangesSummary(nodes, iHLFontColor, iHLBkColor, uiHLFontStyle, uiTriggerType, szTriggerParams);
        }        
        DumpHtmlNodeToString(pChangesSummary, xmlDocGetRootElement(pChangesSummary), szChangesSummary);
        if (szChangesSummary.size() == 0)
        {
            return false;
        }
    }
    return true;
}

void DiffHLEngine::CreateRssFeed(   string& szRssFeed,
                                    const string& szFileName,
                                    const string& szMonitorTitle,
                                    const string& szMonitorUrl,
                                    const unsigned int& uiMonitorID,
                                    const unsigned int& uiResourceID,
                                    const unsigned int& uiQtyTriggers,
                                    const string& szChangesSummary)
{
    xmlDocPtr newDoc = xmlReadFile(szFileName.c_str(), NULL, XML_PARSE_RECOVER | XML_PARSE_NOERROR | XML_PARSE_NOWARNING | XML_PARSE_NOBLANKS);
    if (newDoc)
    {
        xmlNodePtr channel = NULL; 
        GetFirstNodeByName(xmlDocGetRootElement(newDoc), "channel", channel);
        if (channel)
        {
            VariablesMap variables;
            stringstream variable;
            string szDateTime;

            GetGMTDateTime(szDateTime);
            variables.insert(VariablesMapPair("[%monitor_title%]", szMonitorTitle));
            variables.insert(VariablesMapPair("[%monitor_url%]", szMonitorUrl));
            variables.insert(VariablesMapPair("[%current_time%]", szDateTime));
            variable.str("");
            variable << uiMonitorID;
            variables.insert(VariablesMapPair("[%monitor_id%]", variable.str()));  
            variable.str("");
            variable << uiResourceID;
            variables.insert(VariablesMapPair("[%resource_id%]", variable.str()));  
            variable.str("");
            variable << uiQtyTriggers;
            variables.insert(VariablesMapPair("[%changes_count%]", variable.str()));
            variables.insert(VariablesMapPair("[%changes_summary%]", szChangesSummary));            

            vector<XmlNodeHolder> textNodes;
            GetAllTextNodes(xmlDocGetRootElement(newDoc), textNodes);
            for (unsigned int i = 0; i < textNodes.size(); i++)
            {
                string szContent((const char*)textNodes[i].node->content);
                ReplaceVariables(szContent, variables);
                xmlNodeSetContent(textNodes[i].node, (const xmlChar*)szContent.c_str());
            }

            vector<XmlNodeHolder> cdataNodes;
            GetAllCDataNodes(xmlDocGetRootElement(newDoc), cdataNodes);
            for (unsigned int i = 0; i < cdataNodes.size(); i++)
            {
                string szContent((const char*)cdataNodes[i].node->content);
                ReplaceVariables(szContent, variables);
                xmlNodeSetContent(cdataNodes[i].node, (const xmlChar*)szContent.c_str());
            }

            xmlDocPtr oldDoc = xmlReadMemory(szRssFeed.c_str(), szRssFeed.size(), NULL, NULL, XML_PARSE_RECOVER | XML_PARSE_NOERROR | XML_PARSE_NOWARNING | XML_PARSE_NOBLANKS);
            if (oldDoc)
            {
                vector<xmlNodePtr> items;
                GetAllNodesByName(xmlDocGetRootElement(oldDoc), "item", items);
                for (unsigned int i = 0; (i < items.size() && i < MAX_FEED_SIZE-1); i++)
                {
                    xmlNodePtr item = xmlCopyNode(items[i], 1);
                    if (item)
                        xmlAddChild(channel, item);
                }
                xmlFreeDoc(oldDoc);
            }
        }
        szRssFeed.erase();
        DumpXmlDocToString(newDoc, szRssFeed);
        xmlFreeDoc(newDoc);
    }
}

void DiffHLEngine::CreateAtomFeed(  string& szAtomFeed,
                                    const string& szFileName,
                                    const string& szMonitorTitle,
                                    const string& szMonitorUrl,
                                    const unsigned int& uiMonitorID,
                                    const unsigned int& uiResourceID,
                                    const unsigned int& uiQtyTriggers,
                                    const string& szChangesSummary)
{
    xmlDocPtr newDoc = xmlReadFile(szFileName.c_str(), NULL, XML_PARSE_RECOVER | XML_PARSE_NOERROR | XML_PARSE_NOWARNING | XML_PARSE_NOBLANKS);
    if (newDoc)
    {
        xmlNodePtr feed = NULL; 
        GetFirstNodeByName(xmlDocGetRootElement(newDoc), "feed", feed);
        if (feed)
        {
            VariablesMap variables;
            stringstream variable;
            string szDateTime;

            GetGMTDateTime(szDateTime);
            variables.insert(VariablesMapPair("[%monitor_title%]", szMonitorTitle));
            variables.insert(VariablesMapPair("[%monitor_url%]", szMonitorUrl));
            variables.insert(VariablesMapPair("[%current_time%]", szDateTime));
            variable.str("");
            variable << uiMonitorID;
            variables.insert(VariablesMapPair("[%monitor_id%]", variable.str()));  
            variable.str("");
            variable << uiResourceID;
            variables.insert(VariablesMapPair("[%resource_id%]", variable.str()));  
            variable.str("");
            variable << uiQtyTriggers;
            variables.insert(VariablesMapPair("[%changes_count%]", variable.str()));
            variables.insert(VariablesMapPair("[%changes_summary%]", szChangesSummary));

            vector<XmlNodeHolder> textNodes;
            GetAllTextNodes(xmlDocGetRootElement(newDoc), textNodes);
            for (unsigned int i = 0; i < textNodes.size(); i++)
            {
                string szContent((const char*)textNodes[i].node->content);
                ReplaceVariables(szContent, variables);
                xmlNodeSetContent(textNodes[i].node, (const xmlChar*)szContent.c_str());
            }

            vector<xmlNodePtr> linksNodes;
            GetAllNodesByName(xmlDocGetRootElement(newDoc), "link", linksNodes);
            for (unsigned int i = 0; i < linksNodes.size(); i++)
            {
                xmlChar* href = xmlGetProp(linksNodes[i], (const xmlChar*)"href");
                if (href)
                {
                    string szHref((const char*)href);
                    ReplaceVariables(szHref, variables);
                    xmlSetProp(linksNodes[i], (const xmlChar*)"href", (const xmlChar*)szHref.c_str());
                    xmlFree(href);
                }
            }
            xmlDocPtr oldDoc = xmlReadMemory(szAtomFeed.c_str(), szAtomFeed.size(), NULL, NULL, XML_PARSE_RECOVER | XML_PARSE_NOERROR | XML_PARSE_NOWARNING | XML_PARSE_NOBLANKS);
            if (oldDoc)
            {
                vector<xmlNodePtr> entries;
                GetAllNodesByName(xmlDocGetRootElement(oldDoc), "entry", entries);
                for (unsigned int i = 0; (i < entries.size() && i < MAX_FEED_SIZE-1); i++)
                {
                    xmlNodePtr entry = xmlCopyNode(entries[i], 1);
                    if (entry)
                        xmlAddChild(feed, entry);
                }
                xmlFreeDoc(oldDoc);
            }
        }
        szAtomFeed.erase();
        DumpXmlDocToString(newDoc, szAtomFeed);
        xmlFreeDoc(newDoc);
    }
}

void DiffHLEngine::InternalProcess(     const bool& bHighLightChanges,
                                        vector<XmlNodeHolder>& nodes,
                                        const bool& bIsFirstDHL,
                                        const string& szRegex,
                                        const int& iHLFontColor,
                                        const int& iHLBkColor,
                                        const unsigned int& uiHLFontStyle,
                                        xmlDocPtr& pChangesSummary,
                                        unsigned int& uiQtyTriggers,
                                        unsigned int& uiQtyAnchors)
{
    pcre *re;
    const char *error;
    int erroffset;
    int ovector[OVECCOUNT];
    int rc;
    int iHLInvFontColor = iHLFontColor;
    int iHLInvBkColor = iHLBkColor;
    
    if (iHLFontColor != -1)
        iHLInvFontColor = iHLFontColor ^ 0x00FFFFFF;
        
    if (iHLBkColor != -1)
        iHLInvBkColor = iHLBkColor ^ 0x00FFFFFF;

    const unsigned char* table = pcre_maketables();
    re = pcre_compile(  szRegex.c_str(),
                        PCRE_CASELESS | PCRE_UTF8,
                        &error,
                        &erroffset,
                        table);
    if (re == NULL)
    {
        free((void*)table);
        return;
    }

    for (unsigned long i = 0; i < nodes.size(); i++)
    {
        xmlNodePtr curNode = nodes[i].node;
        unsigned int uiOffset = 0;
        
        if (bHighLightChanges && !bIsFirstDHL)
        {
            AddToChangesSummary(curNode, pChangesSummary);
            xmlNodePtr hlRootNode = NULL;
            xmlNodePtr hlLastNode = NULL;
            CreateHighLightNodes(iHLFontColor, iHLBkColor, uiHLFontStyle, hlRootNode, hlLastNode);
            if (hlRootNode && hlLastNode)
            {
                xmlReplaceNode(curNode, hlRootNode);
                xmlAddChild(hlLastNode, curNode);
            }
        }
        
        do
        {
            rc = pcre_exec( re,
                            NULL,
                            (const char*)curNode->content,
                            xmlStrlen(curNode->content),
                            uiOffset,
                            0,
                            ovector,
                            OVECCOUNT);
            if (rc > 0 && ovector[0] == 0 && ovector[1] == 0)
                break;
            if (rc > 0)
            {
                if (bHighLightChanges)
                {
                    if (bIsFirstDHL &&  !uiOffset && curNode == nodes[i].node)
                    {
                        AddToChangesSummary(curNode, pChangesSummary);
                        xmlNodePtr hlRootNode = NULL;
                        xmlNodePtr hlLastNode = NULL;
                        CreateHighLightNodes(iHLFontColor, iHLBkColor, uiHLFontStyle, hlRootNode, hlLastNode);
                        if (hlRootNode && hlLastNode)
                        {
                            xmlReplaceNode(curNode, hlRootNode);
                            xmlAddChild(hlLastNode, curNode);
                        }
                    }

                    xmlNodePtr hlRootNode = NULL;
                    xmlNodePtr hlLastNode = NULL;
                    CreateHighLightNodes(iHLInvFontColor, iHLInvBkColor, uiHLFontStyle, hlRootNode, hlLastNode);
                    if (hlRootNode && hlLastNode)
                    {                        
                        xmlNodePtr firstTextNode = xmlNewTextLen(curNode->content, ovector[0]);
                        xmlNodePtr hlTextNode = xmlNewTextLen(curNode->content+ovector[0], ovector[1]-ovector[0]);
                        xmlNodePtr lastTextNode = xmlNewText(curNode->content+ovector[1]);

                        xmlReplaceNode(curNode, firstTextNode);
                        xmlFreeNode(curNode);
                        xmlAddNextSibling(firstTextNode, hlRootNode);
                        xmlAddChild(hlLastNode, hlTextNode);
                        xmlAddNextSibling(hlRootNode, lastTextNode);
                        
                        InsertW3MLinkNode(hlTextNode, uiQtyAnchors);

                        curNode = lastTextNode;
                    }
                    else
                    {
                        uiOffset = ovector[1];
                    }
                }
                else
                {
                    uiOffset = ovector[1];
                }
                uiQtyTriggers++;
            }
        }
        while (rc > 0);
    }
    free((void*)table);
    free(re);
}

void DiffHLEngine::HighLightAnyWordFromList(    vector<XmlNodeHolder>& nodes,
                                                const bool& bIsFirstDHL,
                                                const string& szWordsList,
                                                const int& iHLFontColor,
                                                const int& iHLBkColor,
                                                const unsigned int& uiHLFontStyle,
                                                xmlDocPtr& pChangesSummary,
                                                unsigned int& uiQtyTriggers,
                                                unsigned int& uiQtyAnchors)
{
    vector<string> words;
    string szRegex;
    StringToWords(szWordsList, ' ', words);
    for (unsigned long i = 0; i < words.size(); i++)
    {
        EscapeString(words[i]);
        szRegex += words[i];
        if (i != words.size()-1)
            szRegex += "|";
    }
    uiQtyTriggers = 0;
    InternalProcess(true, nodes, bIsFirstDHL, szRegex, iHLFontColor, iHLBkColor, uiHLFontStyle, pChangesSummary, uiQtyTriggers, uiQtyAnchors);
}


void DiffHLEngine::HighLightAllWordsFromList(   vector<XmlNodeHolder>& nodes,
                                                const bool& bIsFirstDHL,
                                                const string& szWordsList,
                                                const int& iHLFontColor,
                                                const int& iHLBkColor,
                                                const unsigned int& uiHLFontStyle,
                                                xmlDocPtr& pChangesSummary,
                                                unsigned int& uiQtyTriggers,
                                                unsigned int& uiQtyAnchors)
{
    vector<string> words;
    string szRegex;
    bool bAllWordsFinded = true;
    uiQtyTriggers = 0;
    StringToWords(szWordsList, ' ', words);
    for (unsigned long i = 0; (i < words.size() && bAllWordsFinded); i++)
    {
        unsigned int uiQtyTriggersTemp = 0;
        unsigned int uiQtyAnchorsTemp = 0;
        xmlDocPtr pTempSummaryChanges;
        szRegex = words[i];
        EscapeString(szRegex);
        InternalProcess(false, nodes, bIsFirstDHL, szRegex, iHLFontColor, iHLBkColor, uiHLFontStyle, pTempSummaryChanges, uiQtyTriggersTemp, uiQtyAnchorsTemp);
        if (uiQtyAnchorsTemp)
            bAllWordsFinded = true;
        else
            bAllWordsFinded = false;
    }    
    if (bAllWordsFinded)
        HighLightAnyWordFromList(nodes, bIsFirstDHL, szWordsList, iHLFontColor, iHLBkColor, uiHLFontStyle, pChangesSummary, uiQtyTriggers, uiQtyAnchors);
}

void DiffHLEngine::HighLightPhrase(     vector<XmlNodeHolder>& nodes,
                                        const bool& bIsFirstDHL,
                                        const string& szPhrase,
                                        const int& iHLFontColor,
                                        const int& iHLBkColor,
                                        const unsigned int& uiHLFontStyle,
                                        xmlDocPtr& pChangesSummary,
                                        unsigned int& uiQtyTriggers,
                                        unsigned int& uiQtyAnchors)
{
    string szRegex;
    szRegex = szPhrase;
    EscapeString(szRegex);
    uiQtyTriggers = 0;
    InternalProcess(true, nodes, bIsFirstDHL, szRegex, iHLFontColor, iHLBkColor, uiHLFontStyle, pChangesSummary, uiQtyTriggers, uiQtyAnchors);
}

void DiffHLEngine::HighLightRegex(  vector<XmlNodeHolder>& nodes,
                                    const bool& bIsFirstDHL,
                                    const string& szRegex,
                                    const int& iHLFontColor,
                                    const int& iHLBkColor,
                                    const unsigned int& uiHLFontStyle,
                                    xmlDocPtr& pChangesSummary,
                                    unsigned int& uiQtyTriggers,
                                    unsigned int& uiQtyAnchors)
{
    uiQtyTriggers = 0;
    InternalProcess(true, nodes, bIsFirstDHL, szRegex, iHLFontColor, iHLBkColor, uiHLFontStyle, pChangesSummary, uiQtyTriggers, uiQtyAnchors);
}

void DiffHLEngine::HighLightChangesSummary(     vector<XmlNodeHolder>& nodes,
                                                const int& iHLFontColor, const int& iHLBkColor, const unsigned int& uiHLFontStyle,
                                                const unsigned int& uiTriggerType, const string& szTriggerParams)
{
    string szRegex;
    switch (uiTriggerType & 0x0000FFFF)
    {
    case TRIGGER_ANY_WORD_FROM_LIST:
    case TRIGGER_ALL_WORDS_FROM_LIST:
        {
            vector<string> words;
            StringToWords(szTriggerParams, ' ', words);
            for (unsigned long i = 0; i < words.size(); i++)
            {
                EscapeString(words[i]);
                szRegex += words[i];
                if (i != words.size()-1)
                    szRegex += "|";
            }
            break;
        }
    case TRIGGER_PHRASE:
        {
            szRegex = szTriggerParams;
            EscapeString(szRegex);
            break;
        }
    case TRIGGER_REGEX:
        {
            szRegex = szTriggerParams;
            break;
        }
    default:
        return;
    }

    pcre *re;
    const char *error;
    int erroffset;
    int ovector[OVECCOUNT];
    int rc;

    int iHLInvFontColor = iHLFontColor;
    int iHLInvBkColor = iHLBkColor;

    if (iHLFontColor != -1)
        iHLInvFontColor = iHLFontColor ^ 0x00FFFFFF;

    if (iHLBkColor != -1)
        iHLInvBkColor = iHLBkColor ^ 0x00FFFFFF;

    const unsigned char* table = pcre_maketables();
    re = pcre_compile(  szRegex.c_str(),
                        PCRE_CASELESS | PCRE_UTF8,
                        &error,
                        &erroffset,
                        table);
    if (re == NULL)
    {
        free((void*)table);
        return;
    }

    for (unsigned long i = 0; i < nodes.size(); i++)
    {
        xmlNodePtr curNode = nodes[i].node;
        unsigned int uiOffset = 0;
        do
        {
            rc = pcre_exec( re,
                            NULL,
                            (const char*)curNode->content,
                            xmlStrlen(curNode->content),
                            uiOffset,
                            0,
                            ovector,
                            OVECCOUNT);
            if (rc > 0 && ovector[0] == 0 && ovector[1] == 0)
                break;
            if (rc > 0)
            {
                xmlNodePtr hlRootNode = NULL;
                xmlNodePtr hlLastNode = NULL;
                CreateHighLightNodes(iHLInvFontColor, iHLInvBkColor, uiHLFontStyle, hlRootNode, hlLastNode);
                if (hlRootNode && hlLastNode)
                {
                    xmlNodePtr firstTextNode = xmlNewTextLen(curNode->content, ovector[0]);
                    xmlNodePtr hlTextNode = xmlNewTextLen(curNode->content+ovector[0], ovector[1]-ovector[0]);
                    xmlNodePtr lastTextNode = xmlNewText(curNode->content+ovector[1]);                        

                    xmlReplaceNode(curNode, firstTextNode);
                    xmlFreeNode(curNode);
                    xmlAddNextSibling(firstTextNode, hlRootNode);
                    xmlAddChild(hlLastNode, hlTextNode);
                    xmlAddNextSibling(hlRootNode, lastTextNode);

                    curNode = lastTextNode;
                }
                else
                {
                    uiOffset = ovector[1];
                }
            }
        }
        while (rc > 0);
    }
    free((void*)table);
    free(re);    
}

void DiffHLEngine::ProcessXSLT(const string& szXml, string& szHtml, unsigned int& uiDetailedContentType)
{
    szHtml = "";
    xmlDocPtr html = NULL;
    xsltStylesheetPtr xsl = NULL;
    xmlDocPtr xml = xmlReadMemory(szXml.c_str(), szXml.size(), NULL, NULL, XML_PARSE_RECOVER | XML_PARSE_NOERROR | XML_PARSE_NOWARNING | XML_PARSE_NOBLANKS);
    if (!xml)
        return;
    xmlNodePtr root = xmlDocGetRootElement(xml);
    if (root)
    {
        if (_stricmp ((const char*)root->name, "rdf") == 0)
        {
            uiDetailedContentType = CONTENT_TYPE_XML | CONTENT_TYPE_RSS;
            xsl = xsltParseStylesheetFile((const xmlChar*)"rss1.xsl");
            if (!xsl)
            {
                xmlFreeDoc(xml);
                return;
            }
        }
        else
        if (_stricmp ((const char*)root->name, "rss") == 0)
        {
            uiDetailedContentType = CONTENT_TYPE_XML | CONTENT_TYPE_RSS;
            xsl = xsltParseStylesheetFile((const xmlChar*)"rss2.xsl");
            if (!xsl)
            {
                xmlFreeDoc(xml);
                return;
            }
        }
        else
        if (_stricmp ((const char*)root->name, "feed") == 0)
        {
            uiDetailedContentType = CONTENT_TYPE_XML | CONTENT_TYPE_ATOM;
            xsl = xsltParseStylesheetFile((const xmlChar*)"atom.xsl");
            if (!xsl)
            {
                xmlFreeDoc(xml);
                return;
            }
        }
        else
        {
            uiDetailedContentType = CONTENT_TYPE_XML;
            xmlFreeDoc(xml);
            return;
        }

        html = xsltApplyStylesheet(xsl, xml, NULL);
        if (!html)
        {
            xsltFreeStylesheet(xsl);
            xmlFreeDoc(xml);
            return;
        }

        DumpHtmlDocToString(html, szHtml);
        string::size_type pos;
        pos = szHtml.find("&lt;", 0);
        while(pos != string::npos)
        {
            szHtml.replace(pos, 4, "<");
            pos = szHtml.find("&lt;", 0);
        }
        pos = szHtml.find("&gt;", 0);
        while(pos != string::npos)
        {
            szHtml.replace(pos, 4, ">");
            pos = szHtml.find("&gt;", 0);
        }
        pos = szHtml.find("&amp;", 0);
        while(pos != string::npos)
        {
            szHtml.replace(pos, 5, "&");
            pos = szHtml.find("&amp;", 0);
        }
    }   

    if (html)
        xmlFreeDoc(html);
    if (xsl)
        xsltFreeStylesheet(xsl);
    if (xml)
        xmlFreeDoc(xml);
}

void DiffHLEngine::AddToChangesSummary(xmlNodePtr node, xmlDocPtr& pChangesSummary)
{
    if (node && pChangesSummary)
    {
        xmlNodePtr root = xmlDocGetRootElement(pChangesSummary);
        if (root)
        {
            bool bBreakPresent = false;
            vector<xmlNodePtr> curNodePath;
            GetNodePath(node, curNodePath);
            unsigned int i, j;

            for (i = 0; i < curNodePath.size() && i < nodePath.size(); i++)
            {
                if (curNodePath[i] != nodePath[i] && i > 0)
                {
                    break;
                }
            }

            if (i != 0)
            {
                for (j = i; j < curNodePath.size() && !bBreakPresent; j++)
                {
                    for (unsigned k = 0; k < BREAK_TAGS_COUNT && !bBreakPresent; k++)
                    {
                        if (curNodePath[j]->name && xmlStrcasecmp(curNodePath[j]->name, (const xmlChar*)break_tags[k]) == 0)
                        {
                            bBreakPresent = true;
                        }
                    }
                }
            }

            nodePath = curNodePath;

            xmlChar* uri = NULL;
            xmlNodePtr linkNode = NULL;
            GetParentLinkNode(node, linkNode);
            if (linkNode)
            {
                xmlChar* href = xmlGetProp(linkNode, (const xmlChar*)"href");                
                if (href)
                {
                    if (node->doc == prevHtmlDoc)
                        uri = xmlBuildURI(href, (const xmlChar*)szPrevEffectiveUrl.c_str());
                    else
                    if (node->doc == curHtmlDoc)
                        uri = xmlBuildURI(href, (const xmlChar*)szCurEffectiveUrl.c_str());
                    xmlFree(href);
                }
            }
            if (uri)
            {
                if (!IsURLValid((const char*)uri))
                {
                    xmlFree(uri);
                    uri = NULL;
                }
            }
            if (uri)
            {
                if (bBreakPresent)
                    xmlNewChild(root, NULL, (const xmlChar*)"br", NULL);
                xmlNodePtr link = xmlNewTextChild(root, NULL, (const xmlChar*)"a", (const xmlChar*)node->content);
                xmlSetProp(link, (const xmlChar*)"href", uri);                
                xmlFree(uri);
                xmlAddChild(root, xmlNewText((const xmlChar*)" "));
            }
            else
            {
                if (bBreakPresent)
                    xmlNewChild(root, NULL, (const xmlChar*)"br", NULL);
                xmlAddChild(root, xmlNewText(node->content));
                xmlAddChild(root, xmlNewText((const xmlChar*)" "));
            }
        }
    }
}

void DiffHLEngine::InsertW3MLinkNode(xmlNodePtr node, unsigned int& uiCounter)
{
    char szLinkTag[64];
    memset(szLinkTag, 0, 64);
    sprintf(szLinkTag, "w3m_%u", uiCounter);

    xmlNodePtr linkNode = NULL;
    GetParentLinkNode(node, linkNode);
    if (linkNode)
    {
        if (xmlHasProp(linkNode, (const xmlChar*)"name"))
        {
            xmlNodePtr w3mLinkNode = xmlNewNode(NULL, (const xmlChar*)"a");
            xmlSetProp(w3mLinkNode, (const xmlChar*)"name", (const xmlChar*)szLinkTag);
            xmlAddPrevSibling(linkNode, w3mLinkNode);
        }
        else
        {
            xmlSetProp(linkNode, (const xmlChar*)"name", (const xmlChar*)szLinkTag);
        }
    }
    else
    {
        linkNode = xmlNewNode(NULL, (const xmlChar*)"a");
        xmlSetProp(linkNode, (const xmlChar*)"name", (const xmlChar*)szLinkTag);
        xmlReplaceNode(node, linkNode);
        xmlAddChild(linkNode, node);
    }
    uiCounter++;
}

void DiffHLEngine::GetParentLinkNode(xmlNodePtr node, xmlNodePtr& linkNode)
{
    xmlNodePtr cur_node = node->parent;
    while (cur_node)
    {
        if (cur_node->type == XML_ELEMENT_NODE && cur_node->name && xmlStrcmp(cur_node->name, (const xmlChar*)"a") == 0)
        {
            linkNode = cur_node;
            break;
        }
        cur_node = cur_node->parent;
    }
}

void DiffHLEngine::GetFirstNodeByName(xmlNodePtr node, char* name, xmlNodePtr& resultNode)
{
    xmlNodePtr cur_node = NULL;
    for (cur_node = node; cur_node && resultNode == NULL; cur_node = cur_node->next)
    {
        if (cur_node->type == XML_ELEMENT_NODE && cur_node->name && xmlStrcmp(cur_node->name, (const xmlChar*)name) == 0)
            resultNode = cur_node;
        GetFirstNodeByName(cur_node->children, name, resultNode);
    }
}

void DiffHLEngine::GetAllNodesByName(xmlNodePtr node, const string& name, vector<xmlNodePtr>& nodes)
{
    xmlNodePtr cur_node = NULL;
    for (cur_node = node; cur_node; cur_node = cur_node->next)
    {
        if (cur_node->type == XML_ELEMENT_NODE && cur_node->name && xmlStrcmp(cur_node->name, (const xmlChar*)name.c_str()) == 0)
            nodes.push_back(cur_node);
        GetAllNodesByName(cur_node->children, name, nodes);
    }
}

void DiffHLEngine::GetAllTextNodes(xmlNodePtr node,
                                   vector<XmlNodeHolder>& nodes)
{
    xmlNodePtr cur_node = NULL;
    for (cur_node = node; cur_node; cur_node = cur_node->next)
    {
        if (xmlNodeIsText(cur_node) && !xmlIsBlankNode(cur_node) && cur_node->content && !IsStringEmpty(cur_node->content))
        {
            nodes.push_back(cur_node);
        }
        GetAllTextNodes(cur_node->children, nodes);
    }
}

void DiffHLEngine::GetAllCDataNodes(xmlNodePtr node, vector<XmlNodeHolder>& nodes)
{
    xmlNodePtr cur_node = NULL;
    for (cur_node = node; cur_node; cur_node = cur_node->next)
    {
        if (cur_node->type == XML_CDATA_SECTION_NODE && !xmlIsBlankNode(cur_node) && cur_node->content && !IsStringEmpty(cur_node->content))
        {
            nodes.push_back(cur_node);
        }
        GetAllCDataNodes(cur_node->children, nodes);
    }
}

void DiffHLEngine::GetNodePath(xmlNodePtr node, vector<xmlNodePtr>& nodes)
{
    if (node)
    {
        xmlNodePtr cur_node = NULL;
        for (cur_node = node; cur_node; cur_node = cur_node->prev)
        {
            if (cur_node->type == XML_ELEMENT_NODE)
                nodes.insert(nodes.begin(), cur_node);
        }
        GetNodePath(node->parent, nodes);
    }
}

void DiffHLEngine::InsertBaseTag(xmlNodePtr root, const string& szUrl)
{
    if (root)
    {
        xmlNodePtr baseNode = NULL;
        GetFirstNodeByName(root, "base", baseNode);
        if (!baseNode)
        {
            xmlNodePtr headNode = NULL;
            GetFirstNodeByName(root, "head", headNode);
            if (headNode)
            {
                xmlNodePtr newNode = xmlNewNode(NULL, (const xmlChar*)"base");
                xmlNewProp(newNode, (const xmlChar*)"href", (const xmlChar*)szUrl.c_str());
                xmlAddPrevSibling(headNode->children, newNode);
            }
        }
        else
        {
            if (!xmlHasProp(baseNode, (const xmlChar*)"href"))
            {
                xmlNewProp(baseNode, (const xmlChar*)"href", (const xmlChar*)szUrl.c_str());
            }
        }
    }
}

void DiffHLEngine::InsertXPathRegionTag(xmlNodePtr root, const int& uiColor)
{
    if (root)
    {
        xmlChar* style = xmlGetProp(root, (const xmlChar*)"style");
        if (style)
        {
            string szStyle((const char*)style);
            char szBorderStyle[64];
            memset(szBorderStyle, 0, 64);
            sprintf(szBorderStyle, ";border: solid 3px #%06X", uiColor);
            szStyle += szBorderStyle;
            xmlSetProp(root, (const xmlChar*)"style", (const xmlChar*)szStyle.c_str());
            xmlFree(style);
        }
        else
        {
            char szBorderStyle[64];
            memset(szBorderStyle, 0, 64);
            sprintf(szBorderStyle, "border: solid 3px #%06X", uiColor);
            xmlNewProp(root, (const xmlChar*)"style", (const xmlChar*)szBorderStyle);
        }
    }
}

void DiffHLEngine::GetContentCharsetFromResource(const string& szResource, string& szCharset)
{
    htmlDocPtr htmlDoc = htmlReadMemory(szResource.c_str(), szResource.size(), NULL, NULL, HTML_PARSE_RECOVER | HTML_PARSE_NOBLANKS | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING);
    if (htmlDoc)
    {
        const xmlChar* enc = htmlGetMetaEncoding(htmlDoc);
        if (enc)
        {
            while (*enc && *enc != ';' && *enc != ' ' && *enc != '\r' && *enc != '\n')
            {
                szCharset += *enc;
                enc++;
            }
        }
        xmlFreeDoc(htmlDoc);
    }
}

void DiffHLEngine::CreateHighLightNodes(   const int& iHLFontColor,
                                           const int& iHLBkColor,
                                           const unsigned int& uiHLFontStyle,
                                           xmlNodePtr& node, xmlNodePtr& last)
{
    xmlNodePtr rootNode = NULL;
    xmlNodePtr lastNode = NULL;
    if (uiHLFontStyle & HIGHLIGHT_FONT_STYLE_BOLD)
    {
        if (lastNode)
        {
            xmlNodePtr newNode = xmlNewChild(lastNode, NULL, (const xmlChar*)"b", NULL);
            lastNode = newNode;
        }
        else
        {
            xmlNodePtr newNode = xmlNewNode(NULL, (const xmlChar*)"b");
            rootNode = newNode;
            lastNode = newNode;
        }
    }
    if (uiHLFontStyle & HIGHLIGHT_FONT_STYLE_ITALIC)
    {
        if (lastNode)
        {
            xmlNodePtr newNode = xmlNewChild(lastNode, NULL, (const xmlChar*)"i", NULL);
            lastNode = newNode;
        }
        else
        {
            xmlNodePtr newNode = xmlNewNode(NULL, (const xmlChar*)"i");
            rootNode = newNode;
            lastNode = newNode;
        }
    }
    if (uiHLFontStyle & HIGHLIGHT_FONT_STYLE_UNDERLINED)
    {
        if (lastNode)
        {
            xmlNodePtr newNode = xmlNewChild(lastNode, NULL, (const xmlChar*)"u", NULL);
            lastNode = newNode;
        }
        else
        {
            xmlNodePtr newNode = xmlNewNode(NULL, (const xmlChar*)"u");
            rootNode = newNode;
            lastNode = newNode;
        }
    }
    if (uiHLFontStyle & HIGHLIGHT_FONT_STYLE_STRIKE)
    {
        if (lastNode)
        {
            xmlNodePtr newNode = xmlNewChild(lastNode, NULL, (const xmlChar*)"s", NULL);
            lastNode = newNode;
        }
        else
        {
            xmlNodePtr newNode = xmlNewNode(NULL, (const xmlChar*)"s");
            rootNode = newNode;
            lastNode = newNode;
         }
    }

    char szFontStyle[64];
    memset(szFontStyle, 0, 64);
    if (iHLFontColor != -1 && iHLBkColor != -1)
    {
        sprintf(szFontStyle, "color:#%06X; background-color:#%06X;", iHLFontColor, iHLBkColor);
    }
    else
    if (iHLFontColor != -1)
    {
        sprintf(szFontStyle, "color:#%06X;", iHLFontColor);
    }
    else
    if (iHLBkColor != -1)
    {
        sprintf(szFontStyle, "background-color:#%06X;", iHLBkColor);
    }
    if (szFontStyle[0])
    {
        if (lastNode)
        {
            xmlNodePtr newNode = xmlNewChild(lastNode, NULL, (const xmlChar*)"font", NULL);
            xmlNewProp(newNode, (const xmlChar*)"style", (const xmlChar*)szFontStyle);
            lastNode = newNode;
        }
        else
        {
            xmlNodePtr newNode = xmlNewNode(NULL, (const xmlChar*)"font");
            xmlNewProp(newNode, (const xmlChar*)"style", (const xmlChar*)szFontStyle);
            rootNode = newNode;
            lastNode = newNode;
         }
    }
    if (rootNode && lastNode)
    {
        node = rootNode;
        last = lastNode;
    }    
}

void DiffHLEngine::DumpHtmlDocToString(xmlDocPtr doc, string& szString)
{
    if (doc)
    {
        char szFileName[MAX_PATH];
        sprintf(szFileName, "%s%u.html", config_downloader_temp_directory.c_str(), _TID_);
        if (htmlSaveFileFormat(szFileName, doc, NULL, 1) != -1)
        {
            FILE *outstream = NULL;
            if((outstream = fopen(szFileName, "rb")) != NULL)
            {
                char ch;
                while (fread(&ch, sizeof(char), 1, outstream))
                {
                    szString += ch;
                }
                fclose(outstream);            
            }            
        }
        unlink(szFileName);
    }
}

void DiffHLEngine::DumpXmlDocToString(xmlDocPtr doc, string& szString)
{
    if (doc)
    {
        char szFileName[MAX_PATH];
        sprintf(szFileName, "%s%u.xml", config_downloader_temp_directory.c_str(), _TID_);
        if (xmlSaveFormatFileEnc(szFileName, doc, NULL, 1) != -1)
        {
            FILE *outstream = NULL;
            if((outstream = fopen(szFileName, "rb")) != NULL)
            {
                char ch;
                while (fread(&ch, sizeof(char), 1, outstream))
                {
                    szString += ch;
                }
                fclose(outstream);
            }            
        }
        unlink(szFileName);
    }
}

void DiffHLEngine::DumpHtmlNodeToString(xmlDocPtr doc, xmlNodePtr node, string& szString)
{
    if (doc && node)
    {
        xmlOutputBufferPtr buf = xmlAllocOutputBuffer(NULL);
        if (buf)
        {
            htmlNodeDumpFormatOutput(buf, doc, node, NULL, 1);
            if (buf->buffer && buf->buffer->use)
            {
                szString.assign((const char*)buf->buffer->content, buf->buffer->use);
            }
            xmlOutputBufferClose(buf);
        }
    }
}

void DiffHLEngine::EscapeString(string& szString)
{
    char symbols[] = {'\\', '^', '$', '.', '[', ']', '|', '(', ')', '?', '*', '+', '-', '{', '}'};
    for (int i = 0; i < sizeof(symbols)/sizeof(char); i++)
    {
        string::size_type pos = szString.find(symbols[i], 0);
        while(pos != string::npos)
        {
            szString.insert(pos, "\\");
            pos = szString.find(symbols[i], pos+2);
        }
    }
}

void DiffHLEngine::MergeResultToString(const MergerResultVector& mergeResult, string& szString)
{
    for (unsigned int i = 0; i < mergeResult.size(); i++)
    {
        char szItem[128];
        sprintf(szItem, "%u,%u,%u,%u", mergeResult[i].offs, mergeResult[i].len, mergeResult[i].src, mergeResult[i].status);
        szString += szItem;
        if (i != mergeResult.size()-1)
            szString += '|';
    }
}

void DiffHLEngine::StringToMergeResult(const string& szString, MergerResultVector& mergeResult)
{
    istringstream strmItems(szString);
    string szItems;
    while(getline(strmItems, szItems, '|'))
    {
        istringstream strmItem(szItems);
        string szItem;
        MergerResultItem mergeItem(0, 0, 0, MIS_NO_CHANGES);
        int i = 0;
        while(getline(strmItem, szItem, ',') && i < 4)
        {
            if (i == 0) mergeItem.offs = atoi(szItem.c_str());
            if (i == 1) mergeItem.len = atoi(szItem.c_str());
            if (i == 2) mergeItem.src = atoi(szItem.c_str());
            if (i == 3) mergeItem.status = (EnumMergeItemStatus)atoi(szItem.c_str());
            i++;            
        }
        mergeResult.push_back(mergeItem);
    }
}